#pragma once

#include <memory>
#include <mutex>

namespace decision::adas {

template <typename T>
class RcuStorage {
public:
    void reset(std::shared_ptr<T> store = nullptr)
    {
        std::lock_guard<std::mutex> guard(lock_);
        store_ = store;
    }

    bool empty() const
    {
        std::lock_guard<std::mutex> guard(lock_);
        return (nullptr == store_);
    }

    const std::shared_ptr<const T> content() const
    {
        std::lock_guard<std::mutex> guard(lock_);
        return store_;
    }

    const std::shared_ptr<T> mutable_content(std::unique_lock<std::mutex>& holder, bool clone = true)
    {
        std::unique_lock<std::mutex> guard(lock_);
        holder.swap(guard);
        if (nullptr == store_) {
            store_ = std::make_shared<T>();
        }
        if (!store_.unique()) {
            store_ = clone ? std::make_shared<T>(*store_) : std::make_shared<T>();
        }
        return store_;
    }

    RcuStorage() = default;

    RcuStorage(const RcuStorage& other)
    {
        std::lock_guard<std::mutex> guard(lock_);
        std::lock_guard<std::mutex> guard_other(other.lock_);
        store_ = other.store_;
    }

    RcuStorage& operator=(const RcuStorage& other)
    {
        if (&other == this) {
            return *this;
        }

        std::lock_guard<std::mutex> guard(lock_);
        std::lock_guard<std::mutex> guard_other(other.lock_);
        store_ = other.store_;
        return *this;
    }

private:
    RcuStorage(const RcuStorage&& other) = delete;
    RcuStorage& operator=(const RcuStorage&&) = delete;

private:
    mutable std::mutex lock_;
    std::shared_ptr<T> store_;
};

}